package com.igoal.utils;

import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.Locale;
import java.util.TimeZone;

import com.igoal.dto.UserGoalPhaseDTO;

/**
 * Helper for Date type.
 * @author Tri Nguyen
 * @version 1.0
 */
public class CalendarUtils {
	public static final String DD_MM_YYYY = "dd/MM/yyyy";
	
	/**
	 * Get the start of the date.
	 * @param date 
	 * @return the date object with time is 00:00 AM.
	 * @throws IllegalArgumentException
	 */
	public static Date getStartDate(Date date) throws IllegalArgumentException{
		if(date == null){
			throw new IllegalArgumentException("Argument date must be not null");
		}
		Calendar calendar = Calendar.getInstance();
		calendar.setTime(date);
		calendar.set(calendar.get(Calendar.YEAR), calendar.get(Calendar.MONDAY), calendar.get(Calendar.DATE), 0, 0, 0);		
		return calendar.getTime();
	}
	
	/**
	 * Get the end of the date.
	 * @param date
	 * @return the date object with time is 23:59 AM.
	 * @throws IllegalArgumentException
	 */
	public static Date getEndDate(Date date) throws IllegalArgumentException{
		if(date == null){
			throw new IllegalArgumentException("Argument date must be not null");
		}
		Calendar calendar = Calendar.getInstance();
		calendar.setTime(date);
		calendar.setTime(date);
		calendar.set(calendar.get(Calendar.YEAR), calendar.get(Calendar.MONDAY), calendar.get(Calendar.DATE), 23, 59, 59);
		return calendar.getTime();
	}
	
	/**
	 * 
	 * @param hours
	 * @param minutes
	 * @return current date with hours and minutes from arguments.
	 */
	public static Date getDateTime(int hours, int minutes){		
		if((hours >= 0 && hours < 24) && (minutes >=0 && minutes <= 60)){
			Calendar calendar = Calendar.getInstance();
			calendar.set(Calendar.HOUR_OF_DAY, hours);
			calendar.set(Calendar.MINUTE, minutes);
			return calendar.getTime();
		}else{
			return null;
		}
	}
	
	/**
	 * 
	 * @param date
	 * @param hours
	 * @param minutes
	 * @return date with date, hours and minutes from arguments.
	 */
	public static Date getDateTime(Date date, int hours, int minutes){
		if(date == null){
			return null;
		}
		if((hours >= 0 && hours < 24) && (minutes >=0 && minutes <= 60)){
			Calendar calendar = Calendar.getInstance();
			calendar.setTime(date);
			calendar.set(Calendar.HOUR_OF_DAY, hours);
			calendar.set(Calendar.MINUTE, minutes);
			return calendar.getTime();
		}else{
			return null;
		}
	}
	
	/**
	 * Format the date.
	 * @param sDate ex: 25/12/2007
	 * @param format ex: dd/MM/yyyy
	 * @return formatted date.
	 */
	public static Date parseDate(String sDate, String format){
		if (sDate == null) return null;
		
		if(sDate.trim().equals("")){
			return null;
		}					
		Date date = null;
		try{		
			SimpleDateFormat sdf = new SimpleDateFormat(format);
			if(sdf != null){
				date = sdf.parse(sDate);				
			}
		}catch (IllegalArgumentException e) {
			e.printStackTrace();
		}
		catch (ParseException e) {	
			e.printStackTrace();
		}
		return date;
	}
	
	/**
	 * Check if the date argument is in the same date with today.
	 * @param date
	 * @return true if in the same date.
	 */
	public static boolean isInToday(Date date){
		if(date == null){
			return false;
		}
		Date today = Calendar.getInstance().getTime();
		return isInTheSameDay(date, today);
	}
	
	/**
	 * Check if two date arguments is in the same day.
	 * @param date1
	 * @param date2
	 * @return true if they are in the same day.
	 */
	public static boolean isInTheSameDay(Date date1, Date date2){
		if(date1 == null || date2 == null){
			return false;
		}
		String format = "MM/dd/yyyy";
		String sDate1 = formatDate(date1, format);
		String sDate2 = formatDate(date2, format);
		return sDate1.equalsIgnoreCase(sDate2);
	}
	
	/**
	 * Format the date.
	 * @param date
	 * @param format ex: dd/mm/yyyy
	 * @return formatted date
	 */
	public static String formatDate(Date date, String format){
		if(date == null){
			return null;
		}							
		try{		
			Locale usLocale = new Locale("en","US");
			DateFormat formatter = new SimpleDateFormat(format, usLocale);
			return formatter.format(date);
		}catch (IllegalArgumentException e) {			
		}		
		return null;
	}
	
	/**
	 * 
	 * @param srcLocale the ID for a TimeZone, either an abbreviation such as "PST", a full name such as "America/Los_Angeles", or a custom ID such as "GMT-8:00". 
	 * @param targetLocale the ID for a TimeZone, either an abbreviation such as "PST", a full name such as "America/Los_Angeles", or a custom ID such as "GMT-8:00".
	 * @param time
	 * @return the String date with format MM/dd/yyyy HH:mm:ss after converted or null if one of arguments is null
	 */
	public static String changeTimeZone(String srcLocale, String targetLocale, String time){				 
		if(srcLocale == null || targetLocale == null || time == null){
			return null;
		}
		DateFormat df = new SimpleDateFormat("MM/dd/yyyy HH:mm:ss");				
		
		try{
			TimeZone srcTZ = TimeZone.getTimeZone(srcLocale);
			TimeZone targetTZ = TimeZone.getTimeZone(targetLocale);
			
			df.setTimeZone(srcTZ);
			Date date = df.parse(time);
			
			df.setTimeZone(targetTZ);
			return df.format(date);
		}catch (Exception e) {
			e.printStackTrace();
		}
		
		return null;
	}
	
	/**
	 * 
	 * @param sequenceDate the string date MM/dd/yyyy HH:mm:ss
	 * @return null if argument is null or the format is not match
	 */
	public static String getDateFromSequenceDate(String sequenceDate){
		if(sequenceDate == null || sequenceDate.length() != 19){
			return null;
		}
		sequenceDate = sequenceDate.trim();
		int space = sequenceDate.indexOf(" ");
		if(space != 10){
			return null;
		}		
		String date = sequenceDate.substring(0, space);
		if(date.matches("^(\\d{1,2}/){2}\\d{4}")){
			return date;
		}
		return null;
	}
	
	/**
	 * 
	 * @param sequenceDate the string date MM/dd/yyyy HH:mm:ss
	 * @return -1 if argument is null or the format is not match
	 */
	public static int getHoursFromSequenceDate(String sequenceDate){		
		String time = getTimeFromSequenceDate(sequenceDate);		
		if(time != null){
			String hours = time.substring(0, 2);
			return ConvertUtils.toInt(hours, -1);
		}
		return -1;
	}
	
	/**
	 * 
	 * @param sequenceDate the string date MM/dd/yyyy HH:mm:ss
	 * @return -1 if argument is null or the format is not match
	 */
	public static int getMinutesFromSequenceDate(String sequenceDate){		
		String time = getTimeFromSequenceDate(sequenceDate);
		if(time != null){
			String minutes = time.substring(3, 5);
			return ConvertUtils.toInt(minutes, -1);
		}
		return -1;
	}
	
	/**
	 * 
	 * @param sequenceDate the string date MM/dd/yyyy HH:mm:ss
	 * @return null if argument is null or the format is not match
	 */
	private static String getTimeFromSequenceDate(String sequenceDate){
		if(sequenceDate == null || sequenceDate.length() != 19){
			return null;
		}
		sequenceDate = sequenceDate.trim();
		int space = sequenceDate.indexOf(" ");
		if(space != 10){
			return null;
		}
		String time = sequenceDate.substring(space).trim();
		if(time.matches("^(\\d{1,2}:){2}\\d{1,2}")){
			return time;
		}
		return null;
	}
	
	/**
	 * 
	 * @param dbValue ex: GM+07:00_1
	 * @return GM+07:00
	 */
	public static String getTimeZoneValueFromDatabaseReturn(String dbValue){
		if(dbValue == null){
			return null;
		}
		int underscore = dbValue.indexOf("_");
		if(underscore > 0){
			return dbValue.substring(0, underscore);
		}
		return null;
	}
	
	/**
	 * The date format must be "MM/dd/yyyy"
	 * @param sDate1
	 * @param sDate2
	 * @return 
	 * <ul>
	 * <li>0 if date1 equal date2
	 * <li>1 if date1 after date2
	 * <li>-1 if date1 before date2
	 * <li>-2 if incorrect
	 * </ul>
	 */
	public static int compareDate(String sDate1, String sDate2){
		if(sDate1 == null || sDate2 == null){
			return -2;
		}
		String format = "MM/dd/yyyy";
		Date dateF = parseDate(sDate1, format);
		if(dateF == null){
			return -2;
		}
		Date dateS = parseDate(sDate2, format);
		if(dateS == null){
			return -2;
		}		
		return dateF.compareTo(dateS);
	}
	
	/**
	 * Get hours from date.
	 * @param date
	 * @return hours
	 */
	public static int getHours(Date date){
		if(date == null){
			return -1;
		}
		Calendar calendar = Calendar.getInstance();
		calendar.setTime(date);
		return calendar.get(Calendar.HOUR_OF_DAY);
	}
	
	/**
	 * Get minutes from date.
	 * @param date
	 * @return minutes
	 */
	public static int getMinutes(Date date){
		if(date == null){
			return -1;
		}
		Calendar calendar = Calendar.getInstance();
		calendar.setTime(date);
		return calendar.get(Calendar.MINUTE);
	}
	
	/**
	 * 
	 * @param timeZoneId1 must be: GMT or GMT+xx:xx or GMT-xx:xx (xx: number)
	 * @param timeZoneId2 must be: GMT or GMT+xx:xx or GMT-xx:xx (xx: number)
	 * @return	The number of minutes that <code>timeZoneId1</code> is greater than <code>timeZoneId2</code>
	 * 			<br/> or NULL if any arguments is not valid
	 */
	public static Integer compareTimeZones(String timeZoneId1, String timeZoneId2){
		if(timeZoneId1 == null || timeZoneId2 == null){
			return null;
		}
		String zeroTZReg = "^GMT$";
		String normalTZReg = "^GMT(\\+|-)[0-9]{1,2}:[0-9]{1,2}$";
		if(!timeZoneId1.matches(zeroTZReg) && !timeZoneId1.matches(normalTZReg)){
			return null;
		}
		if(!timeZoneId2.matches(zeroTZReg) && !timeZoneId2.matches(normalTZReg)){
			return null;
		}		
		String op1,op2,ham1,ham2;
		if(timeZoneId1.matches(zeroTZReg)){
			op1 = "+";
			ham1 = "00:00";
		}else{
			op1 = timeZoneId1.substring(3,4);
			ham1 = timeZoneId1.substring(4);			
		}
		if(timeZoneId2.matches(zeroTZReg)){
			op2 = "+";
			ham2 = "00:00";
		}else{
			op2 = timeZoneId2.substring(3,4);
			ham2 = timeZoneId2.substring(4);
		}		
		return subtract(ham1, op1, ham2, op2);
	}
	
	private static Integer subtract(String sHoursAndMinutes1, String operation1, String sHoursAndMinutes2, String operation2){
		if(sHoursAndMinutes1 == null || sHoursAndMinutes2 == null){
			return null;
		}
		try{
			int hours1 = getHours(sHoursAndMinutes1).intValue();
			int hours2 = getHours(sHoursAndMinutes2).intValue();
			int minutes1 = getMinutes(sHoursAndMinutes1).intValue();
			int minutes2 = getMinutes(sHoursAndMinutes2).intValue();
			if(hours1 > 23 || hours1 < 0 || hours2 > 23 || hours2 < 0){
				return null;
			}
			if(minutes1 > 59 || minutes1 < 0 || minutes2 > 59 || minutes2 < 0){
				return null;
			}
			hours1 = operation1.equals("-") ? (0-hours1) : hours1;
			minutes1 = operation1.equals("-") ? (0-minutes1) : minutes1;
			hours2 = operation2.equals("-") ? (0-hours2) : hours2;
			minutes2 = operation2.equals("-") ? (0-minutes2) : minutes2;			
			int minutesDifference = (hours2*60+minutes2) - (hours1*60+minutes1);
			return new Integer(minutesDifference);
		}catch (NullPointerException e) {
			return null;
		}
	}
		
	private static Integer getHours(String sHoursAndMinutes){
		if(sHoursAndMinutes == null){
			return null;
		}
		String hamReg = "^[0-9]{1,2}:[0-9]{1,2}$";
		if(!sHoursAndMinutes.matches(hamReg)){
			return null;
		}
		int colonPos = sHoursAndMinutes.indexOf(":");
		if(colonPos < 0){
			return 24;
		}
		String hours = sHoursAndMinutes.substring(0, colonPos);
		return ConvertUtils.toInt(hours, 24);
	}
	
	private static Integer getMinutes(String sHoursAndMinutes){
		if(sHoursAndMinutes == null){
			return null;
		}
		String hamReg = "^[0-9]{1,2}:[0-9]{1,2}$";
		if(!sHoursAndMinutes.matches(hamReg)){
			return null;
		}
		int colonPos = sHoursAndMinutes.indexOf(":");
		if(colonPos < 0){
			return 60;
		}
		String minutes = sHoursAndMinutes.substring(colonPos+1);
		return ConvertUtils.toInt(minutes, 60);
	}
	
	/**
	 * Convert date from inputFormat to outputFormat.
	 * @param sDate
	 * @param sInputFormat
	 * @param sOutputFormat
	 * @return converted date.
	 * @throws ParseException
	 */
	public static String convert(final String sDate,
                                 final String sInputFormat,
                                 final String sOutputFormat) throws ParseException {

		SimpleDateFormat format = new SimpleDateFormat(sInputFormat);
		Date date = format.parse(sDate);
		format = new SimpleDateFormat(sOutputFormat);
		return format.format(date);
	}
	
	public static void setSytemTimeZone(String sTimeZone){
		TimeZone tz = TimeZone.getTimeZone(sTimeZone);		
		if(!sTimeZone.equals("GMT") && TimeZone.getTimeZone("GMT").equals(tz)){
			throw new IllegalArgumentException(sTimeZone + " is incorrect!"); 
		}
		TimeZone.setDefault(tz);
	}
	
	/**
	 * Calculate how many minute that the @param date before now
	 * @param date
	 * @return -1 if date is null or date is after now
	 */
	public static long calculateMinutesDifferentBeforeNow(Date date){
		if(date == null) return -1;
		
		Date now = new Date();
		if(now.before(date)) return -1;
		
		long diff = now.getTime() - date.getTime();
		long minuteDiff = diff / (1000 * 60);		
		return minuteDiff;
	}	
	
	/**
	 * 
	 * @param dateTime
	 * @param displayMonth
	 * @param displayYear
	 * @return
	 */
	public static String getCreatedDateString(Date dateTime, String displayMonth, String displayYear){
		if(dateTime == null) return "";
		Calendar calendar = Calendar.getInstance();
		calendar.setTime(dateTime);
		int date = calendar.get(Calendar.DATE);
		int month = calendar.get(Calendar.MONTH);
		int year = calendar.get(Calendar.YEAR);			
		
		String createdDate = date + displayMonth + month + displayYear + year;
		return createdDate;
	}
	
	public static int checkTaskDeadLine(Date deadLine){
		if(deadLine == null)
			return UserGoalPhaseDTO.NONE_DEADLINE;
		
		Date today = Calendar.getInstance().getTime();
		today = getStartDate(today);
		deadLine = getStartDate(deadLine);
		
		if(deadLine.after(today)){
			Date tmpCompareDate = addNumberOfDaysTo(today, UserGoalPhaseDTO.WARNING_DATE_NUMBER);
			if(tmpCompareDate.compareTo(deadLine) >= 0){
				return UserGoalPhaseDTO.WARNING_DEADLINE;
			}
			return UserGoalPhaseDTO.NORMAL_DEADLINE;
		}else{
			return UserGoalPhaseDTO.MISSED_DEADLINE;
		}
	}
	
	public static Date addNumberOfDaysTo(Date date, int amount){
		Calendar calendar = Calendar.getInstance();
		calendar.setTime(date);
		calendar.add(Calendar.DATE, amount);
		date = calendar.getTime();
		
		return date;		
	}
	
	public static int getNumberOfCommingDate(Date date){
		Date today = Calendar.getInstance().getTime();
		
		if(today.after(date) || date == null)
			return 0;
		
		long diff = date.getTime() - today.getTime();
		long minuteDiff = diff / (1000 * 60);
		long dateDiff = minuteDiff / (60 * 24);
		
		return (int)dateDiff;
	}
}
